﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace NetworkSimulator
{
    public class LinkManager:ITickerListener
    {
        // Synchronize object
        private static object lockingObject = new object();

        private static LinkManager singleTonObject;
        private Ticker ticker;
        private List<Request> releaseList;
        private List<Request> results;

        // To manage requests in links
        private Dictionary<Link, List<Request>> requestsInLink;

        // To manage release time of a request
        private Dictionary<Request, long> releaseTime;

        public List<Request> ReleaseList
        {
            get { return releaseList; }
        }

        public static LinkManager instance()
        {
            if (singleTonObject == null)
            {
                lock (lockingObject)
                {
                    if (singleTonObject == null)
                        singleTonObject = new LinkManager();
                }
            }
            return singleTonObject;
        }

        public LinkManager()
        {
            ticker = Ticker.instance();
            ticker.addListener(this);
            releaseList = new List<Request>();
            results = new List<Request>();
            requestsInLink = new Dictionary<Link, List<Request>>();
            releaseTime = new Dictionary<Request, long>();

            Thread.CurrentThread.Priority = ThreadPriority.Highest;
        }

        public void serveRequest(Request request)
        {
            lock (lockingObject)
            {
                if (request.Path.Links.Count > 0)
                {
                    releaseTime[request] = ticker.ElapsedTime + request.HoldingTime;

                    foreach (Link link in request.Path.Links)
                    {
                        //allocate bandwidth
                        link.UsingBandwidth += request.Bandwidth;

                        // Holding request
                        if (!requestsInLink.ContainsKey(link))
                            requestsInLink[link] = new List<Request>();
                        requestsInLink[link].Add(request);
                    }
                    releaseList.Add(request);
                    releaseList = releaseList.OrderBy(o => releaseTime[o]).ToList();
                }
                else
                {
                    results.Add(request);
                }
            }
            if (releaseList.Count == 100)
            {
                turnOffLink(getLinkById("410"));
            }
        }

        public void onTickerTick()
        {
            if (releaseList.Count > 0)
            {
                Request request = releaseList[0];
                while (releaseTime[request] <= ticker.ElapsedTime)
                {
                    doRelease(request);
                    releaseList.RemoveAt(0);
                    if (releaseList.Count == 0) break;
                    request = releaseList[0];
                }
            }
        }

        private void doRelease(Request request)
        {
            lock (Topology.lockingObject)
            {
                //Console.WriteLine(release);
                foreach (Link link in request.Path.Links)
                {
                    requestsInLink[link].Remove(request);
                    link.UsingBandwidth -= request.Bandwidth;
                }
            }
            results.Add(request);
            if (results.Count == RequestDispatcher.instance().NumOfRequest)
            { 
                //TODO:
                ticker.stop();
                Console.WriteLine(Node.acceptedCount.ToString());
                Console.WriteLine((Node.acceptedCount / 2000.0) * 100 + "%");
            }
        }

        // Turn a link off
        // author: CH
        public void turnOffLink(Link link)
        {
            lock (lockingObject)
            {
                List<Request> rerouteRequests = new List<Request>();

                // Copy request from requestsInLink[link] to rerouteRequests, 
                // we must do this step, because we will remove all requests from  requestsInLink[link]
                rerouteRequests.AddRange(requestsInLink[link]);

                // Now, get request in rerouteRequests one by one to reroute it.
                foreach (Request req in rerouteRequests)
                {
                    // Remove request in release list 
                    releaseList.Remove(req);

                    // Remove it in requestInLink
                    foreach (Link l in req.Path.Links)
                    {
                        requestsInLink[l].Remove(req);
                        // Release bandwidth
                        l.UsingBandwidth -= req.Bandwidth;
                    }

                    // put it to reroute node
                    req.HoldingTime = releaseTime[req] - ticker.ElapsedTime;
                    req.Path.Links[0].Source.rerouteRequest(req);
                }
                // Now turn it off
                link.turnOff();
            }
        }

        // Get a link by id
        // author: CH
        private Link getLinkById(string id)
        {
            Link result = null;
            foreach (Link link in Topology.getAllLinks())
            {
                if (link.ID == id)
                {
                    result = link;
                    break;
                }
            }
            return result;
        }
    }
}
